/*******************************************************************************
 * @file Copyright 2009 The CREATE Lab at CMU
 * 
 *       This file is part of JubJub, an open source project.
 *       http://code.google.com/p/jubjub/
 * 
 *       Redistribution and use in source and binary forms, with or without
 *       modification, are permitted provided that the following conditions are
 *       met:
 * 
 *       Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 * 
 *       Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 * 
 *       Neither the name of Carnegie Mellon nor the names of its contributors
 *       may be used to endorse or promote products derived from this software
 *       without specific prior written permission.
 * 
 *       THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *       "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *       LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 *       PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 *       HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *       SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *       LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *       DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *       THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *       (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 *       OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *******************************************************************************/

package edu.cmu.ri.createlab.jj.blocks;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import edu.cmu.ri.createlab.jj.JubJub.EmptyStringException;
import edu.cmu.ri.createlab.jj.blocks.BlockDefs.*;
import edu.cmu.ri.createlab.jj.blocks.TaggedCodePiece.TCPType;
import edu.cmu.ri.createlab.jj.visitors.*;
import edu.cmu.ri.createlab.jj.projects.*;

/**
 * 
 * @author Erik Pasternak
 *
 * FlowBlock defines program flow snippets.
 * 
 * FlowBlock allows different program flow structures to be defined. A FlowBlock
 * defines a beginning snippet of code, a middle snippet of code, a last middle
 * snippet of code, and a closing snippet of code. It also lets the number of
 * times the middle should be duplicated to be set. This allows for arbitrary
 * expansion of flow structures like switch and if-else-if. The IDE is responsible
 * for deciding how it will handle these and the first version will likely only
 * support very specific FlowBlocks.
 */
public class FlowBlock implements BlockInterface
{
   
   /**
    * \defgroup BlockVars These variables are common to all BlockInterface types.
    */
   /*@{*/

   ///The name given to this block. CodeBlock and BlockBlock types should have unique names
   private String name;
   ///The JJClass which these blocks belong to and where they will be written to
   ///when output as code/xml
   private JJClass classOwner;
   ///What type of block this is from the list of BlockTypes
   private BlockTypes type;
   ///A unique ID for this block
   private int id;
   ///A list of RefBlock instances that reference this block.
   private List<RefBlock> refs;
   ///Whether this block is still valid or not
   private boolean valid;
   ///comment for this block
   private String comment;
   
   
   /*@}*/
   
   /**
    * An array of tagged code lists. There should be four lists, one each for
    * the starting code, the middle code, the last middle code, and the closing
    * code. (example: 'if(boolean foo)', 'else if(boolean bar)', 'else', '')
    */
   private List<TaggedCodePiece>[] code;
   
   /**
    * An array of VarBlock elements. This defines what variables are needed
    * for each section of code and therefore should be an array of length 4.
    * Since flow blocks do not return anything only parameters are used.
    */
   private List<VarBlock>[] params;
   
   /**
    * Defines how many middles should occur. n-1 middles and 1 last middle will be created,
    * so if this is set to 1 only a last middle will be created.
    */
   private int middles;
   /**
    * SwapNames are a temp setting for the variable names to be used by a reference.
    * They are used the same way as in CodeBlock but there are four arrays of them,
    * one for each code list.
    */
   private String[][] swapNames;
   /**
    * The type of flow control this is, if, ifelse, while, for, switch, etc.
    */
   private FlowTypes fType;
   
   /**
    * 
    * @param name The name of the block.
    * @param owner The class this block belongs to
    * @param ID A unique ID for this block
    * 
    * Creates a FlowBlock with the given parameters.
    * 
    * This creates a new FlowBlock and sets up the arrays for its data. The number
    * of middles(if > 0), params, and code all need to be set separately if using
    * this constructor.
    */
   @SuppressWarnings("unchecked")
   public FlowBlock(String name, JJClass owner, int ID)
   {
      this.setName(name);
      this.valid = true;
      this.classOwner = owner;
      this.id = ID;
      this.type = BlockTypes.FLOW;
      this.params = new LinkedList[4];
      this.code = new LinkedList[4];
      this.swapNames = new String[4][];
      this.middles = 0;
      for(int i = 0; i < 4; i++)
      {
         this.params[i] = new LinkedList<VarBlock>();
         this.code[i] = new LinkedList<TaggedCodePiece>();
         this.swapNames[i] = null;
      }
      this.refs = new LinkedList<RefBlock>();
      this.comment = "";
      this.fType = FlowTypes.WHILE;
   }

   /**
    * 
    * @param name The name of the block.
    * @param owner The class this block belongs to
    * @param ID A unique ID for this block
    * 
    * Creates a FlowBlock with the given parameters.
    * 
    * This creates a new FlowBlock and sets up the arrays for its data. The 
    * params and code need to be set separately if using this constructor.
    */
   @SuppressWarnings("unchecked")
   public FlowBlock(String name, JJClass owner, int ID, int middles)
   {
      this.setName(name);
      this.valid = true;
      this.classOwner = owner;
      this.id = ID;
      this.type = BlockTypes.FLOW;
      this.params = new LinkedList[4];
      this.code = new LinkedList[4];
      this.swapNames = new String[4][];
      this.middles = middles;
      for(int i = 0; i < 4; i++)
      {
         this.params[i] = new LinkedList<VarBlock>();
         this.code[i] = new LinkedList<TaggedCodePiece>();
         this.swapNames[i] = null;
      }
      this.refs = new LinkedList<RefBlock>();
      this.comment = "";
      this.fType = FlowTypes.WHILE;
   }
   
   /**
    * 
    * @param name The name of the block.
    * @param owner The class this block belongs to
    * @param ID A unique ID for this block
    * 
    * Creates a FlowBlock with the given parameters.
    * 
    * This creates a new FlowBlock and sets up the arrays for its data. The 
    * code needs to be set separately if using this constructor.
    */
   @SuppressWarnings("unchecked")
   public FlowBlock(String name, JJClass owner, int ID, int middles, 
         LinkedList<TaggedCodePiece>[] code)
   {
      this.setName(name);
      this.valid = true;
      this.classOwner = owner;
      this.id = ID;
      this.type = BlockTypes.FLOW;
      this.params = new LinkedList[4];
      this.code = new LinkedList[4];
      this.swapNames = new String[4][];
      this.middles = middles;
      for(int i = 0; i < 4; i++)
      {
         this.params[i] = new LinkedList<VarBlock>();
         this.code[i] = new LinkedList<TaggedCodePiece>();
         this.swapNames[i] = null;
         setCode(code[i], i);
      }
      this.refs = new LinkedList<RefBlock>();
      this.comment = "";
      this.fType = FlowTypes.WHILE;
   }
   
   public FlowBlock clone(int id) throws Exception
   {
      FlowBlock fb = new FlowBlock(getName(), getOwner(), id, getMiddles());
      for(int i = 0; i < 4; i++)
      {
         Iterator<VarBlock> vIt = getParams(i).iterator();
         Iterator<TaggedCodePiece> it = getCode(i).iterator();
         LinkedList<VarBlock> params = new LinkedList<VarBlock>();
         
         while(vIt.hasNext()) fb.addParam(vIt.next().clone(id),i);
         
         LinkedList<TaggedCodePiece> code = new LinkedList<TaggedCodePiece>();
         
         while(it.hasNext())
         {
            TaggedCodePiece piece = it.next();
            if(piece.getType() == TCPType.CODE) code.add(new TaggedCodePiece(piece.getContent()));
            else code.add(new TaggedCodePiece(piece.getContent(), piece.getIndex()));
         }
         fb.setCode(code, i);
         
      }
      return fb;
   }
   
   ///Sets one set of code for the flow block
   public void setCode(List<TaggedCodePiece> code, int index)
   {
      if(code == null) return;
      Iterator<TaggedCodePiece> it = code.iterator();
      this.code[index].clear();
      while(it.hasNext())
         this.code[index].add(it.next());
   }
   
   ///Returns one set of code for the flow block
   public List<TaggedCodePiece> getCode(int index)
   {
      return code[index];
   }
   
   public void setFType(FlowTypes fType)
   {
      this.fType = fType;
   }
   
   public FlowTypes getFType()
   {
      return fType;
   }

   ///Sets one set of parameters for the flow block
   ///FlowBlock parameters should never be referenced and forgetting
   ///them shouldn't be an issue.
   public void setParams(List<VarBlock> params, int index)
   {
      
      Iterator<VarBlock> it;
      if(this.params[index].size() > 0)
      {
         it = this.params[index].iterator();
         while(it.hasNext())
            it.next().destroyBlock();
      }
      it = params.iterator();
      this.params[index].clear();
      while(it.hasNext())
         this.params[index].add(it.next());
   }
   
   ///Adds the given parameter to the given list
   public void addParam(VarBlock param, int index)
   {
      this.params[index].add(param);
   }
   
   ///removes the given param from the given list
   public void remParam(VarBlock param, int index)
   {
      this.params[index].remove(param);
   }

   ///returns one set of parameters for the flow block
   public List<VarBlock> getParams(int index)
   {
      return params[index];
   }

   ///Sets the number of middles that occur in this flowblock
   public void setMiddles(int middles)
   {
      this.middles = middles;
   }

   ///returns the number of middles that occur in this flow block
   public int getMiddles()
   {
      return middles;
   }

   ///Sets the swapNames for the specified set
   public void setSwapNames(String[] swapNames, int index)
   {
      this.swapNames[index] = swapNames;
   }

   ///returns the swapNames for the specified set
   public String[] getSwapNames(int index)
   {
      return swapNames[index];
   }

   /**
    * \defgroup BlockMethods These methods are common to all blocks.
    */
   /*@{*/

   ///Sets the comment for this block
   public void setComment(String comment)
   {
      this.comment = comment;
   }
   ///Gets the comment for this block
   public String getComment()
   {
      return comment;
   }
   
   ///Return whether this block is still valid or if it is deleted pending
   ///removal of all its refs.
   public boolean isValid()
   {
      return valid;
   }
   
   ///Sets this block to not Valid and updates all its refs
   public void deleteBlock()
   {
      valid = false;
      this.updateRefs();
   }
   
   ///Sets this block to Valid and updates all its refs
   public void undeleteBlock()
   {
      valid = true;
      this.updateRefs();
   }
   
   ///Accepts a BlockVisitorInterface and visits itself
   public BVReturn accept(BlockVisitorInterface bv)
   {
      bv.visit(this);
      return null;
   }
   
   ///returns the JJClass that owns this block
   
   public JJClass getOwner()
   {
      return classOwner;
   }

   ///returns the id for this block
   
   public int getID()
   {
      return id;
   }

   ///returns the name of this block
   
   public String getName()
   {
      return name;
   }

   ///returns the BlockTypes type of this block
   
   public BlockTypes getType()
   {
      return type;
   }
   
   ///FlowBlock doesn't own any references so it never has an error
   ///The RefBlock referencing this block might have an error, but that
   ///doesn't matter to the FlowBlock.
   public boolean hasError()
   {
      return false;
   }
   
   ///Since it owns no refs it doesn't have anything to check for errors
   public void checkError()
   {
      return;
   }
   
   ///This makes it easier for the garbage collector to remove this block
   ///by ensuring all the references it contains are removed.
   public void destroyBlock()
   {
      this.setOwner(null);
      for(int i = 0; i < params.length; i++)
      {
         Iterator<VarBlock> it = params[i].iterator();
         while(it.hasNext())
         {
            it.next().destroyBlock();
         }
         params[i].clear();
      }
      Iterator<RefBlock> it = refs.iterator();
      while(it.hasNext())
      {
         it.next().clearBlock();
      }
      refs.clear();
   }

   ///sets the JJClass that owns this block
   
   public void setOwner(JJClass owner)
   {
      this.classOwner = owner;
      
   }

   ///sets the name of this block as well as its callName.
   
   public void setName(String name) throws EmptyStringException
   {
      ///If the name is of 0 length we've made a mistake and need to throw
      ///an exception.
      if(name.length() == 0) 
      {
         throw new EmptyStringException("Empty string name");
      }
      this.name = name;
   }
   
   ///Adds a RefBlock to the list of references
   
   public void addRef(RefBlock ref)
   {
      if(refs.contains(ref)) return;
      refs.add(ref);
   }

   ///Removes the given RefBlock from the list of references
   
   public void remRef(RefBlock ref)
   {
      refs.remove(ref);
   }
   
   ///Returns a list of RefBlock types that reference this block
   public List<RefBlock> getRefs()
   {
      return refs;
   }

   ///Updates all the RefBlock references to this block
   
   public void updateRefs()
   {
      Iterator<RefBlock> it = refs.iterator();
      while(it.hasNext())
      {
         it.next().update();
      }
   }
   
   /*@}*/
   
   ///returns a readable String representation of this block
   public String toString()
   {
      String s = "";
      s += "Type:" + type;
      s += " ID:" + id;
      s += " Name:" + name;
      s += " Owner:" + classOwner;
      s += " NumRefs:" + refs.size();
      s += " Middles:" + middles;
      s += " Valid:" + valid;
      s += "\nParams:";
      for(int i = 0; i < params.length; i++)
      {
         if(params[i] != null)
            for(int j = 0; j < params[i].size(); j++)
               s += params[i].get(j).getName() + ", ";
      }
      for(int i = 0; i < code.length; i++)
      {
         if(code[i] != null)
            for(int j = 0; j < code[i].size(); j++)
               s += code[i].get(j).getContent() + " ";
         s += "\n";
      }
      
      return s;
   }
   
   ///Test function for this block
   public static void main(String[] args)
   {
      
   }



}
